package utils.math_utils;

import components.LIFNeuron;
import components.Synapse;

public class MathUtils {

	public static double euclideanDist(double [] a, double [] b) throws
		ArithmeticException, IllegalArgumentException {
		try {
			lengthCheck(a, b);
		} catch (IllegalArgumentException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
			System.exit(1);
		}
		double dist = 0.0;
		for(int i = 0; i < a.length; i++) {
			dist += (a[i]-b[i]) * (a[i]-b[i]);
		}
		dist = Math.sqrt(dist);
		if (dist == Double.NaN) {
			throw new ArithmeticException("Distance is NaN." +
					" Coordinate or MathUtil ERROR.");
		}
		return dist;
	}
	
	public static double euclideanDist(int [] a, int [] b) throws
	ArithmeticException, IllegalArgumentException {
		try {
			lengthCheck(a, b);
		} catch (IllegalArgumentException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
			System.exit(1);
		}
		double dist = 0.0;
		for(int i = 0; i < a.length; i++) {
			dist += (a[i]-b[i]) * (a[i]-b[i]);
		}
		dist = Math.sqrt(dist);
		if (dist == Double.NaN) {
			throw new ArithmeticException("Distance is NaN." +
					" Coordinate or MathUtil ERROR.");
		}	
		return dist;
	}
	
	public static double euclideanDist(Coordinate s, Coordinate f) throws
	ArithmeticException, IllegalArgumentException {
		int [] a = s.getCoordinate();
		int [] b = f.getCoordinate();
		try {
			lengthCheck(a, b);
		} catch (IllegalArgumentException e) {
			System.err.println(e.getMessage());
			e.printStackTrace();
			System.exit(1);
		}
		double dist = 0.0;
		for(int i = 0; i < a.length; i++) {
			dist += (a[i]-b[i]) * (a[i]-b[i]);
		}
		dist = Math.sqrt(dist);
		if (dist == Double.NaN) {
			throw new ArithmeticException("Distance is NaN." +
					" Coordinate or MathUtil ERROR.");
		}	
		return dist;
	}
	
	public static void lengthCheck(double [] a, double b [])
			throws IllegalArgumentException {
		if(a.length != b.length) {
			throw new IllegalArgumentException("Vector dimensions do not" +
					" agree.");
		}
	}
	
	public static void lengthCheck(int [] a, int b [])
			throws IllegalArgumentException {
		if(a.length != b.length) {
			throw new IllegalArgumentException("Vector dimensions do not" +
					" agree.");
		}
	}
	
	public static Synapse [] extractSyns(Synapse [] arr) {
		int c = numNonNull(arr);
		Synapse [] out = new Synapse[c];
		int j = 0;
		for (int i = 0, n = arr.length; i < n; i++) {
			if (arr[i] != null) {
				out[j] = arr[i];
				j++;
			}
		}
		return out;
	}
	
	/**
	 * Warning, unsafe for non-square 2D arrays.
	 * @param arr
	 * @param col
	 * @return
	 */
	public static Synapse [] extractSynsCol(Synapse [][] arr, int col) {
		Synapse [] array = new Synapse[arr.length];
		for(int i = 0, n = arr.length; i < n; i++) {
			array[i] = arr[i][col];
		}
		return extractSyns(array);
	}
	
	public static int numNonNull(Object[] arr) {
		int count = 0;
		for(int i =0, n = arr.length; i < n; i++) {
			if (arr[i] != null) {
				count++;
			}
		}
		return count;
	}
	
	public static Synapse [] concatArrays(Synapse [] arr1,  Synapse [] arr2) {
		int size = arr1.length + arr2.length;
		Synapse [] out = new Synapse[size];
		for(int i = 0; i < arr1.length; i++) {
			out[i] = arr1[i];
		}
		for(int i = 0; i < arr2.length; i++) {
			out[i + arr1.length] = arr2[i];
		}
		return out;
	}

	public static LIFNeuron [] concatArrays(LIFNeuron [] arr1,  LIFNeuron [] arr2) {
		int size = arr1.length + arr2.length;
		LIFNeuron [] out = new LIFNeuron[size];
		for(int i = 0; i < arr1.length; i++) {
			out[i] = arr1[i];
		}
		for(int i = 0; i < arr2.length; i++) {
			out[i + arr1.length] = arr2[i];
		}
		return out;
	}

	public static double[] concatArrays(double[] arr1, double[] arr2) {
		int size = arr1.length + arr2.length;
		double [] out = new double[size];
		for(int i = 0; i < arr1.length; i++) {
			out[i] = arr1[i];
		}
		for(int i = 0; i < arr2.length; i++) {
			out[i + arr1.length] = arr2[i];
		}
		return out;
	}
	
	public static double [][] arrayAdd2D(double [][] a, double [][] b) {
		if(a.length != b.length){
			throw new IllegalArgumentException("Dimension Mismatch: Rows");
		}
		double [][] c = new double [a.length][a[0].length];
		for(int i = 0, n = a.length; i < n; i++) {
			if(a[i].length != b[i].length){
				throw new IllegalArgumentException("DimensionMismatch: Cols");
			}
			if(a[i].length != a[0].length || b[i].length != b[0].length){
				throw new IllegalArgumentException("Non-uniform columns");
			}
			for(int j = 0, m = a[i].length; j < m; j++){
				c[i][j] = a[i][j] + b[i][j];
			}
		}
		return c;
	}
	
	/**
	 * Adds 2 2D arrays of bytes in place, overwriting the first array.
	 * @param a
	 * @param b
	 */
	public static void arrayAddInPlace2D(byte [][] a, byte [][] b) {
		if(a.length != b.length){
			throw new IllegalArgumentException("Dimension Mismatch: Rows");
		}
		for(int i = 0, n = a.length; i < n; i++) {
			if(a[i].length != b[i].length){
				throw new IllegalArgumentException("DimensionMismatch: Cols");
			}
			if(a[i].length != a[0].length || b[i].length != b[0].length){
				throw new IllegalArgumentException("Non-uniform columns");
			}
			for(int j = 0, m = a[i].length; j < m; j++){
				a[i][j] += b[i][j];
			}
		}
	}
	
	/**
	 * Adds 2 2D arrays of bytes in place, overwriting the first array.
	 * @param a
	 * @param b
	 */
	public static void arrayAddInPlace2D(double [][] a, double [][] b) {
		if(a.length != b.length){
			throw new IllegalArgumentException("Dimension Mismatch: Rows");
		}
		for(int i = 0, n = a.length; i < n; i++) {
			if(a[i].length != b[i].length){
				throw new IllegalArgumentException("DimensionMismatch: Cols");
			}
			if(a[i].length != a[0].length || b[i].length != b[0].length){
				throw new IllegalArgumentException("Non-uniform columns");
			}
			for(int j = 0, m = a[i].length; j < m; j++){
				a[i][j] += b[i][j];
			}
		}
	}
	
	
	/**
	 * Adds two 1D arrays in place such that the first array is overwritten
	 * with the result of the addition.
	 * @param a
	 * @param b
	 */
	public static void arrayAddInPlace1D(double [] a, double [] b) {
		lengthCheck(a, b);
		for(int i = 0, n = a.length; i < n; i++){
			a[i] += b[i];
		}
	}
	
	/**
	 * Adds two 1D arrays in place such that the first array is overwritten
	 * with the result of the addition, and ensures that no values will be 
	 * greater than the specified max value
	 * @param a
	 * @param b
	 * @param max
	 */
	public static void arrayAddInPlace1D(double [] a, double [] b, double max) {
		lengthCheck(a, b);
		for(int i = 0, n = a.length; i < n; i++){
			a[i] += b[i];
			if(a[i] > max){
				a[i] = max;
			}
		}
	}

	/**
	 * 
	 * @param a
	 * @param b
	 */
	public static void dimCheck(double [][] a, double [][] b){
		if(a.length != b.length) {
			throw new IllegalArgumentException("Dimension Mismatch: Rows");
		}
		for(int i = 0; i < a.length; i++) {
			lengthCheck(a[i], b[i]);
		}	
	}
	
	/**
	 * 
	 * @param a
	 */
	public static void checkUniformCols(double [][] a) {
		int l = a[0].length;
		for(int i = 0, n = a.length; i < n; i++) {
			if(a[i].length != l) {
				throw new RuntimeException("Columns are not uniform.");
			}
		}
	}
	
	/**
	 * 
	 * @param a
	 * @param val
	 */
	public static void arrayScaleInPlace2D(double [][] a, double val){
		for(int i = 0, n = a.length; i < n; i++){
			for(int j = 0, m = a[0].length; j < m; j++){
				a[i][j] *= val;
//				if(a[i][j] > 0.85) {
//					a[i][j] = 0;
//				}
			}
		}
	}
	
	/**
	 * 
	 * @param a
	 * @return
	 */
	public static byte [][] toByte(double [][] a) {
		byte [][] b = new byte[a.length][a[0].length];
		
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				b[i][j] = (byte)a[i][j];
			}
			
		}
		return b;
	}
	
	/**
	 * 
	 * @param a
	 * @return
	 */
	public static double[] toDouble(byte[] a) {
		double [] d = new double[a.length];
		
		for(int i = 0; i < a.length; i++){		
			d[i] = (double)a[i];
		}
		return d;
	}	
	
	/**
	 * 
	 * @param a
	 * @return
	 */
	public static double [][] toDouble(byte [][] a) {
		double [][] d = new double[a.length][a[0].length];
		
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[i].length; j++) {
				d[i][j] = (double)a[i][j];
			}
			
		}
		return d;	
	}
	
	/**
	 * 
	 * @param a
	 * @return
	 */
	public static double [][] toDouble(boolean [][] a) {
		double [][] d = new double[a.length][a[0].length];
		
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[i].length; j++) {
				d[i][j] = a[i][j] ? 1.0 : 0.0;
			}
			
		}
		return d;	
	}
	
	/**
	 * 
	 * @param r
	 * @param a
	 */
	public static void addNoise(RandStream r, double [][] a) {
		for(int i = 0, n = a.length; i < n; i++){
			for(int j = 0, m = a[i].length; j < m; j++){
				a[i][j] += r.next();
			}
		}
	}
	
	/**
	 * 
	 * @param a
	 * @return
	 */
	public static double max(double [][] a) {
		double max = Double.MIN_VALUE;		
		for(int i = 0, n = a.length; i < n; i++) {
			for(int j = 0, m = a[i].length; j < m; j++) {
				if(a[i][j] > max) {
					if(a[i][j] != Double.NaN)
						max = a[i][j];
					else
						System.err.println("Array Contains NaN elements");
				}
			}
		}
		if(Double.isInfinite(max)){
			System.out.println("Array contains non-finite elements.");
		}	
		return max;		
	}
	
}